﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.TeamFoundation.VersionControl.Client;

namespace RenameTracker
{
    public static class Tracker
    {        
        struct NameChange
        {
            public string Current;
            public string Prev;
        }        

        /// <summary>
        /// Examine the basic properties returned from TFS.  Then find, condense, and categorize the name changes that
        /// went into this change and store the results in the NameChangeComponents property.
        /// </summary>
        public static IEnumerable<NameChangeComponent> CategorizeNameChangeComponents(ChangeInfo change)
        {
            if (!change.ChangeType.IsNameChange())
            {
                return null;
            }
                        
            var componentNameChanges = GetComponentNameChanges(change);
            return ProcessComponentNameChanges(componentNameChanges);            
        }

        /// <summary>
        /// For each path component of the provided item, we want to know:
        /// (a) whether it changed names in the changeset being considered 
        /// (b) if so, its previous name
        /// note #1 - this is "raw" history, not yet "condensed"
        /// note #2 - the list includes the item itself, not just parents strictly
        /// </summary>
        private static List<NameChange> GetComponentNameChanges(ChangeInfo change)
        {
            var componentNameChanges = new List<NameChange>();
            foreach (var component in change.Path.SplitCumulative('/').Skip(1))  // skip "$"
            {
                var versionTo = new ChangesetVersionSpec(change.Version);
                var componentHistory = (from Changeset c in change.Server.QueryHistory(component, versionTo, change.DeletionId,
                                                            RecursionType.None, null, null, versionTo, 2, true, false)
                                        select new ChangeInfo(c))
                                       .ToList();  // should not throw - parents of a valid item must themselves be valid
                
                // see summary above
                if (componentHistory.Count() == 2
                    && componentHistory[0].ChangeType.IsNameChange()
                    && componentHistory[0].Version == change.Version)
                {
                    componentNameChanges.Add(new NameChange
                    {
                        Current = componentHistory[0].Path,
                        Prev = componentHistory[1].Path
                    });
                }
            }

            return componentNameChanges;
        }

        /// <summary>
        /// Condense the complete list of changes to an item's path components that we observed, to the canonical set of changes that got us here.          
        /// </summary>
        private static List<NameChangeComponent> ProcessComponentNameChanges(List<NameChange> componentNameChanges)
        {
            var nameChangeComponents = new List<NameChangeComponent>();
            var transforms = new List<Func<string, string>>();

            // main loop that categorizes changes in path components as same, renamed, or moved
            // during each iteration, any already-recorded changes are transformed away so that the next change can be isolated
            for (int i = 0; i < componentNameChanges.Count; ++i)
            {                
                // remove known name changes
                string prevName = componentNameChanges[i].Prev, curName = componentNameChanges[i].Current;
                foreach (var transform in transforms)
                {
                    prevName = transform(prevName);
                }

                // test for identity
                if (curName == prevName)
                {
                    continue;
                }
                                
                // rename means the parent paths were identical; move is anything else
                bool isMove = true;
                var prevSplit = prevName.SplitLast('/');
                var currentSplit = curName.SplitLast('/');
                if (prevSplit[0] == currentSplit[0])
                {
                    isMove = false;
                }
                else
                {
                    // if this name change was a move, we don't care about anything that happened to our parents since they weren't
                    // our parents at the time we pended the move
                    nameChangeComponents.Clear();
                }

                // distinguish name changes made directly to the item being queried
                bool isParentChange = i < componentNameChanges.Count - 1;

                nameChangeComponents.Add(new NameChangeComponent(isMove, isParentChange, prevName, curName));
                transforms.Add(x => x.Replace(prevName, curName));
            }

            return nameChangeComponents;
        }
    }
}
